import { PROJECT_UK } from './const'
import { Emit, type EmitEventMap } from './emit'
import { isObject, logError } from './utils'

interface ServerMessageMap extends EmitEventMap {
  connect: void
  inject: void
}

type MessageEventEventMap = {
  [K in keyof ServerMessageMap]: { sender: chrome.runtime.MessageSender; data: ServerMessageMap[K] }
}

export class MessageEvent<T extends keyof ServerMessageMap = keyof ServerMessageMap> {
  uk = PROJECT_UK

  constructor(
    public type: T,
    public data?: ServerMessageMap[T]
  ) {}
}

export class ServerMessageEmit extends Emit<MessageEventEventMap> {
  protected extensionId?: string

  constructor(public isServer: boolean) {
    super()

    chrome?.runtime?.onMessage.addListener((message, sender) => {
      if (isObject<MessageEvent>(message) && message.uk === PROJECT_UK) {
        const { type, data } = message
        this.emit(type, { sender, data })
      }
      return void 0
    })

    if (isServer)
      chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
        if (changeInfo.status === 'complete')
          chrome.tabs.get(tabId).then((tab) => {
            if (tab.url?.startsWith('http')) this.dispatch(new MessageEvent('connect'), tabId)
          }, logError)
      })

    if (!isServer)
      this.once('connect', ({ sender }) => {
        this.extensionId = sender.id
      })
  }

  dispatch(event: MessageEvent, tabId?: number) {
    if (this.isServer) {
      if (!tabId)
        chrome.tabs.query({ url: ['http://*/*', 'https://*/*'] }).then((tabList) => {
          tabList.forEach((tab) => {
            chrome.tabs.sendMessage(tab.id!, event).catch(logError)
          })
        })
      else chrome.tabs.sendMessage(tabId, event).catch(logError)
    } else if (this.extensionId) {
      chrome.runtime.sendMessage(this.extensionId, event).catch(logError)
    }
  }
}
